package com.lsh.oauth2.service.impl;

import com.lsh.common.constant.Constants;
import com.lsh.common.redis.RedisCache;
import com.lsh.oauth2.dto.LoginUser;
import com.lsh.oauth2.entity.SysUser;
import com.lsh.oauth2.exception.BusinessExceptions;
import com.lsh.oauth2.manager.AsyncManager;
import com.lsh.oauth2.manager.factory.AsyncFactory;
import com.lsh.oauth2.service.LoginService;
import com.lsh.oauth2.service.TokenService;
import com.lsh.oauth2.util.MessageUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author ：LiuShihao
 * @date ：Created in 2021/9/17 10:11 上午
 * @desc ：
 */
@Service
public class LoginServiceImpl implements LoginService {
    /**
     * springsecurity也提供了现成的类：
     * AuthenticationManager认证管理器；DecisionManager     决策管理器
     * 认证管理器把任务交给了Provider；决策管理器则把任务交给了Voter
     * spingsecurity提供了多个Provider的实现类，如果我们想用数据库来储存用户的认证数据，那么我们就选择DaoAuthenticationProvider。
     * 对于Voter，我们一般选择RoleVoter就够用了，它会根据我们配置文件中的设置来决定是否允许某一个用户访问制定的Web资源。
     * 而DaoAuthenticationProvider也是不直接操作数据库的，它把任务委托给了UserDetailService
     *
     * 而我们要做的，就是实现这个UserDetailService. 它就是连接我们的数据库和springsecurity的桥梁。
     *
     */
    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private TokenService tokenService;

    /**
     * 登录接口
     * @param username 用户名
     * @param password 密码
     * @param code     验证码（图片验证码）
     * @param uuid     图片验证码的唯一标识
     * @return 返回JWT  token
     */
    @Override
    public String login(String username, String password, String code, String uuid) {
        //captcha_codes:uuid
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        //根据 UUID 获取缓存中的验证码信息
        String captcha = redisCache.getCacheObject(verifyKey);
        //删除缓存中的验证码信息
        redisCache.deleteObject(verifyKey);
        //验证码过期
        if (captcha == null) {
            //异步 定时任务  记录登录信息
            //TODO  MessageUtils.message("user.jcaptcha.expire") 2021年09月17日17:43:30
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new BusinessExceptions("user","user.jcaptcha.expire", null);
        }
        //验证码错误
        if (!code.equalsIgnoreCase(captcha)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new BusinessExceptions("user","user.jcaptcha.error",null);
        }

        // 用户验证
        Authentication authenticate = null;
        try {
            /**
             * 1.根据用户输入的用户名、密码构建了 UsernamePasswordAuthenticationToken，并将其交给 AuthenticationManager 来进行认证处理。
             * 2.AuthenticationManager 本身不包含认证逻辑，其核心是用来管理所有的 AuthenticationProvider，通过交由合适的 AuthenticationProvider 来实现认证。
             */
            // 该方法会去调用 UserDetailsServiceImpl.loadUserByUsername
             authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new BusinessExceptions("user","user.password.not.match",null);
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new BusinessExceptions(e.getMessage());
            }
        }

        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        //获取到代表当前用户的信息 这个对象通常是UserDetails的实例。
        User user = (User)authenticate.getPrincipal();
        LoginUser loginUser = new LoginUser();
        loginUser.setUser(new SysUser(user.getUsername(), user.getPassword()));
        loginUser.setAuthorities(user.getAuthorities());
        // 生成token
        return tokenService.createToken(loginUser);
    }
}
